package com.minivision.fdiot.mqtt.handler;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.minivision.fdiot.common.Action;
import com.minivision.fdiot.common.RestResult;
import com.minivision.fdiot.domain.AppData;
import com.minivision.fdiot.domain.DownloadMsg;
import com.minivision.fdiot.domain.FaceData;
import com.minivision.fdiot.domain.FaceSetAction;
import com.minivision.fdiot.domain.FaceSetData;
import com.minivision.fdiot.domain.RefreshFaceSetsData;
import com.minivision.fdiot.entity.Device;
import com.minivision.fdiot.entity.MqttLog;
import com.minivision.fdiot.exception.ErrorType;
import com.minivision.fdiot.exception.FacePlatException;
import com.minivision.fdiot.mqtt.MqttConnectionManager;
import com.minivision.fdiot.mqtt.RequestFuture;
import com.minivision.fdiot.mqtt.message.UpgradeFile;
import com.minivision.fdiot.protopool.Packet;
import com.minivision.fdiot.protopool.Packet.Head;
import com.minivision.fdiot.protopool.Packet.Head.CmdCode;
import com.minivision.fdiot.protopool.PacketUtils;
import com.minivision.fdiot.mqtt.service.PublishMessageTemplate;
import com.minivision.fdiot.service.DeviceService;
import com.minivision.fdiot.service.DiscoveryService;
import com.minivision.fdiot.service.FaceSetService;
import com.minivision.fdiot.service.MqttLogService;

import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

/**
 * 向设备发送消息
 * @author hughzhao
 * @2018年3月1日
 */
@Service
@Slf4j
public class CommonSender {
  
  @Autowired
  private FaceSetService faceSetService;
  
  @Autowired
  private PacketUtils packetUtils;
  
  @Autowired
  private PublishMessageTemplate messageTemplate;
  
//  @Autowired
//  private Cache downloadTokenCache;

  @Autowired
  private StringRedisTemplate stringRedisTemplate;
  
  @Autowired
  private MqttLogService mqttLogService;
  
  @Autowired
  private ObjectMapper mapper;
  
  @Autowired
  private DeviceService deviceService;
  
  @Autowired
  private MqttConnectionManager connectionManager;
  
  @Autowired
  private RestTemplate restTemplate;
  
  @Autowired
  private DiscoveryService discoveryService;
  
  private MqttLog buildMqttLog(String clientId, String model, int cmdCode, String cmdName, String msgType, String message) {
    MqttLog log = new MqttLog();
    log.setDeviceSn(clientId);
    log.setModel(model);
    log.setCmdCode(cmdCode);
    log.setCmdName(cmdName);
    log.setMsgType(msgType);
    log.setMessage(message);
    return log;
  }

  /**
   * 向设备发送历史消息
   * @param mqttLog
   * @return
   */
  public boolean repush(MqttLog mqttLog) {
    Object message = null;
    try {
      message = mapper.readValue(mqttLog.getMessage(), Class.forName(mqttLog.getMsgType(), false, Thread.currentThread().getContextClassLoader()));
    } catch (ClassNotFoundException | IOException e) {
      log.error("mqtt message deserialize failed, message:" + mqttLog.getMessage() + ", message type:" + mqttLog.getMsgType(), e);
      message = mqttLog.getMessage();
    }
    
    try {
      //发送历史失败消息
      boolean flag = sendRequest(false, mqttLog.getDeviceSn(), mqttLog.getModel(), mqttLog.getCmdCode(), mqttLog.getCmdName(), message, Void.class);
      //发送成功删除记录
      if (flag) {
        mqttLogService.delete(mqttLog.getId());
      }
      return flag;
    } catch (Exception e) {
      log.error("repush to device[" + mqttLog.getDeviceSn() + "] failed, log:" + mqttLog, e);
    }
    
    return false;
  }
  
  /**
   * 向设备转发消息
   * @param mqttLog
   * @return
   */
  public boolean forward(MqttLog mqttLog) {
    Object message = null;
    try {
      message = mapper.readValue(mqttLog.getMessage(), Class.forName(mqttLog.getMsgType(), false, Thread.currentThread().getContextClassLoader()));
    } catch (ClassNotFoundException | IOException e) {
      log.error("mqtt message deserialize failed, message:" + mqttLog.getMessage() + ", message type:" + mqttLog.getMsgType(), e);
      message = mqttLog.getMessage();
    }
    
    try {
      //转发消息
      return sendRequest(mqttLog.getDeviceSn(), mqttLog.getModel(), mqttLog.getCmdCode(), mqttLog.getCmdName(), message, Void.class);
    } catch (Exception e) {
      log.error("forward to device[" + mqttLog.getDeviceSn() + "] failed, log:" + mqttLog, e);
    }
    
    return false;
  }
  
  /**
   * 向设备发送请求消息
   * @param clientId
   * @param model
   * @param cmdCode
   * @param cmdName
   * @param message
   * @param responseBodyType
   * @return
   */
  public <T> boolean sendRequest(String clientId, String model, int cmdCode, String cmdName, Object message, Class<T> responseBodyType) {
    return sendRequest(true, clientId, model, cmdCode, cmdName, message, responseBodyType);
  }
  
  /**
   * 发送请求消息，包括新消息和历史失败消息
   * @param isNew 是否新消息
   * @param clientId
   * @param model
   * @param cmdCode
   * @param cmdName
   * @param message
   * @param responseBodyType
   * @return
   */
  public <T> boolean sendRequest(boolean isNew, String clientId, String model, int cmdCode, String cmdName, Object message, Class<T> responseBodyType) {
    boolean needLog = false;
    Device device = deviceService.findDevice(clientId);
    //设备不在线
    if (!device.getOnline()) {
      log.warn("device[{}] offline, message:{}", clientId, message);
      needLog = true;
    } else if (!connectionManager.isConnected(clientId)) {//设备没有连接到当前服务节点
      log.warn("device[{}] not connected to this server, message:{}", clientId, message);
      //转发请求到该设备已连接的节点
      String node = device.getConnectNode();
      //设备连接到某个节点且该节点在线则转发请求到该节点
      if (StringUtils.hasText(node) /*&& discoveryService.isUp(node)*/) {
        String url = "http://" + node + "/api/v1/device/forward2Device";
        String json = null;
        try {
          json = mapper.writeValueAsString(message);
        } catch (JsonProcessingException e) {
          log.error("message type[" + message.getClass().getSimpleName() + "] can not be serialized to json", e);
          json = "" + message;
        }

        try {
          RestResult<?> forwardResponse = restTemplate.postForObject(url, buildMqttLog(clientId, model, cmdCode, cmdName, message.getClass().getCanonicalName(), json), RestResult.class);
          log.info("message[{}] to device[{}] forward to {}, response:{}", json, clientId, node, forwardResponse.toString());
          if (forwardResponse.getStatus() != 0) {
            needLog = true;
          }
        } catch (Exception e) {
          log.error("can not forward to " + url + ", maybe " + node + " was down...", e);
          needLog = true;
        }
      }

      //设备没有连接信息
      if (!StringUtils.hasText(node)) {
        log.warn("device[{}] online but no connect info", clientId);
        needLog = true;
      }

      //设备连接的节点不在线
      if (!discoveryService.isUp(node)) {
        log.warn("node[{}] was down...", node);
        //deviceService.deviceOffline(clientId, "");
        needLog = true;
      }
    } else {//设备在线且连接到当前节点
      Head h = packetUtils.buildRequestHead(cmdCode);
      try {
        RequestFuture<T> f = messageTemplate.sendRequest(packetUtils.getDeviceAddr(clientId, model), new Packet<>(h, message), responseBodyType);
        Packet<T> response = f.getResponse();
        //设备响应失败
        if (!Packet.Head.Type.isResponseOk(response)) {
          log.warn("device response error:{}", response);
          needLog = true;
        }
      } catch (Exception e) {
        log.error("device response exception", e);
        needLog = true;
      }
    }



    //新消息记录历史日志
    if (isNew && cmdCode != Packet.Head.CmdCode.UPGRADE_FILE && needLog) {
      String json = null;
      try {
        json = mapper.writeValueAsString(message);
      } catch (JsonProcessingException e) {
        log.error("message type[" + message.getClass().getSimpleName() + "] can not be serialized to json", e);
        json = "" + message;
      }
      
      mqttLogService.create(buildMqttLog(clientId, model, cmdCode, cmdName, message.getClass().getCanonicalName(), json));
    }
    
    return !needLog;
  }
  
  /**
   * 向设备发送通知消息
   * @param clientId
   * @param model
   * @param cmdCode
   * @param message
   */
  public void sendNotify(String clientId, String model, int cmdCode, Object message) {
    Head h = packetUtils.buildNotifyHead(cmdCode);
    messageTemplate.sendTo(packetUtils.getDeviceAddr(clientId, model), new Packet<>(h, message));
  }
  
  /**
   * 向设备发送升级文件
   * @param clientId
   * @param model
   * @param message
   */
  public void sendUpgradeFile(String clientId, String model, UpgradeFile message){
    sendRequest(clientId, model, CmdCode.UPGRADE_FILE, "UPGRADE_FILE", message, Void.class);
  }
  
  /**
   * 向设备发送应用数据，需要设备回复
   * @param clientId
   * @param model
   * @param message
   */
  public void sendRawMessage(String clientId, String model, AppData message){
    sendRequest(clientId, model, CmdCode.RAW_MESSAGE, "RAW_MESSAGE", message, Void.class);
  }
  
  /**
   * 向设备发送应用数据，不需要设备回复
   * @param clientId
   * @param model
   * @param message
   */
  public void notifyRawMessage(String clientId, String model, AppData message){
    sendNotify(clientId, model, CmdCode.RAW_MESSAGE, message);
  }
  
  /**
   * 通知设备人脸库操作信息
   * @param clientId
   * @param model
   * @param facesets
   * @param action
   */
  public void refreshFaceSet(String clientId, String model, List<FaceSetData> facesets, FaceSetAction action) {
    RefreshFaceSetsData data = new RefreshFaceSetsData();
    data.setDetail(action);
    data.setFacesets(facesets);
    sendRequest(clientId, model, CmdCode.REFRESH_FACESET, "REFRESH_FACESET", data, Void.class);
  }
  
  /**
   * 通知设备解绑人脸库
   * @param clientId
   * @param model
   */
  public void unbindFaceSet(String clientId, String model, String facesetToken) {
    sendRequest(clientId, model, CmdCode.UNBIND_FACESET, "UNBIND_FACESET", facesetToken, Void.class);
  }
  
  /**
   * 通知设备人脸操作信息
   * @param action
   * @param clientId
   * @param model
   * @param faceDatas
   */
  public void refreshFace(Action action, String clientId, String model, List<FaceData> faceDatas) {
    switch (action) {
      case ADD:
        sendRequest(clientId, model, CmdCode.FACE_ADD, "FACE_ADD", faceDatas, Void.class);
        break;
      case UPDATE:
        sendRequest(clientId, model, CmdCode.FACE_UPDATE, "FACE_UPDATE", faceDatas, Void.class);
        break;
      case DELETE:
        sendRequest(clientId, model, CmdCode.FACE_DELETE, "FACE_DELETE", faceDatas, Void.class);
        break;
      default:
        return;
    }
  }
  
  /**
   * 通知设备同步人脸库
   * @param clientId
   * @param model
   * @param facesetToken
   * @param extras
   * @throws FacePlatException
   */
  public void sync2Device(String clientId, String model, String facesetToken, Map<String, Object> extras) throws FacePlatException {
    log.info("mqtt sender sync2Device starting to push to device[{}], facesetToken:{}", clientId, facesetToken);
    if(faceSetService.exists(facesetToken)){
      String downloadKey = UUID.randomUUID().toString();
      stringRedisTemplate.opsForValue().set(downloadKey,facesetToken);
      //downloadTokenCache.put(new Element(downloadKey, facesetToken));
      
      DownloadMsg msg = new DownloadMsg();
      msg.setDownloadToken(downloadKey);
      msg.setFacesetToken(facesetToken);
      msg.setExtras(extras);
      
      sendRequest(clientId, model, CmdCode.DOWNLOAD_FACESET, "DOWNLOAD_FACESET", msg, Void.class);
    }else{
      throw new FacePlatException(ErrorType.FACESET_NOT_EXIST);
    }
  }
  
}
